Well, since nobody else seems to be able to answer this question, I will do it myself…
It turns out I was using the wrong QLThumbnailGenerator.
override func prepareForReuse()
{
super.prepareForReuse()
if let request = self.request
{
thumbnailGenerator.cancel(request)
self.request = nil
}
imageView?.image = NSImage(imageLiteralResourceName: "Placeholder")
The problem was so subtle. One thing I didn't quote in my original post was that I had a var that holds a private QLThumbnailGenerator instance and it was this that I was using to cancel the request, even though I was using the shared instance in the loadImage method.
As soon as I switched the loadImage method to use the private var, everything started to work better.
I say better because there was still the occasion when the generator would return a nil image for some obscure reason.
So, for those times, I have a helper method that loads those thumbnails the "old fashioned" way…
static func previewOfFile(at url: URL, size: NSSize, asIcon: Bool = false, completion: @escaping (NSImage) -> ())
{
let cfUrl = url as CFURL
guard let imageSource = CGImageSourceCreateWithURL(cfUrl, nil),
let image = getImage(for: imageSource, at: url, size: size) else
{
completion(NSWorkspace.shared.icon(forFile: url.path))
return
}
completion(image)
}
… and the full code now reads…
let thumbnailGenerator = QLThumbnailGenerator()
var request: QLThumbnailGenerator.Request?
func loadImage()
{
imageView?.image = NSImage(imageLiteralResourceName: "Placeholder")
request = QLThumbnailGenerator.Request(fileAt: self.url!, size: self.imageSize, scale: 1.0, representationTypes: [.lowQualityThumbnail])
self.thumbnailGenerator.generateRepresentations(for: self.request!)
{
(thumbnail: QLThumbnailRepresentation?, type: QLThumbnailRepresentation.RepresentationType, error: Error?) -> Void in
DispatchQueue.main.async
{
let transition = CATransition()
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.duration = 0.5
self.imageView?.layer?.add(transition, forKey: nil)
self.imageView?.image = thumbnail?.nsImage
if thumbnail?.nsImage == nil
{
DispatchQueue.main.async
{
ThumbnailImage.previewOfFile(at: self.url!, size: self.imageSize)
{
[unowned self] retrievedImage in
transition.duration = 0.3
self.imageView?.image = retrievedImage
}
}
}
I hope this is of use to anyone else who stumbles across this issue